package lc.animation;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.SortedMap;
import java.util.TreeMap;

import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.util.export.InputCapsule;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;
import com.jme.util.export.OutputCapsule;
import com.jme.util.export.Savable;

/**
 * AnimationDataController provides a shallow wrapper on top of a set of keyframes
 * indexed by time. It doesn't actually work as a controller; it only works as a container
 * for data to be carried in a JME file. Actual animation instance controllers are created
 * when you want to animate a node set (which may be separate from the node set you loaded).
 */
public class AnimationDataController extends Controller implements Externalizable, Savable {

	SortedMap<Float, Keyframe> keyframes;
	String name;
	float endTime;
	public static final int VERSION = 3;

	public AnimationDataController() {
	}

	public AnimationDataController(SortedMap<Float, Keyframe> keyframes, String name, float endTime) {
		this.keyframes = keyframes;
		this.name = name;
		this.endTime = endTime;
	}

	public SortedMap<Float, Keyframe> getKeyframes() {
		return keyframes;
	}

	public final String getName() {
		return name;
	}

	public final float endTime() {
		return endTime;
	}

	@Override
	public void update(float time) {
		//	do nothing -- this controller is for data access only
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		int ver = in.readInt();
		if (ver != VERSION)
			throw new IOException("Bad version found in AnimationDataController.readExternal()");
		name = in.readUTF();
		endTime = in.readFloat();
		int cnt = in.readInt();
		if (cnt > 0) {
			keyframes = new TreeMap<Float, Keyframe>();
			for (int i = 0; i != cnt; ++i) {
				float f = in.readFloat();
				Keyframe k = new Keyframe();
				k.readExternal(in);
				keyframes.put(f, k);
			}
		}
        animation = makeAnimation();
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeInt(VERSION);
		out.writeUTF(name == null ? "" : name);
		out.writeFloat(endTime);
		if (keyframes == null)
			out.writeInt(0);
		else {
			out.writeInt(keyframes.size());
			for (Float f : keyframes.keySet()) {
				out.writeFloat((float)f);
				Keyframe k = keyframes.get(f);
				k.writeExternal(out);
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Class getClassTag() {
		// TODO Auto-generated method stub
		return AnimationDataController.class;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void read(JMEImporter e) throws IOException {
        InputCapsule capsule = e.getCapsule(this);
        byte[] bytes = capsule.readByteArray("keyframes", null);
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        try {
        	readExternal(ois);
        }
        catch (ClassNotFoundException cnfx) {
        	throw new IOException("Class not found in AnimationDataController.read()");
        }
	}

	@Override
	public void write(JMEExporter e) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        writeExternal(oos);
        oos.close();
        OutputCapsule capsule = e.getCapsule(this);
        capsule.write(baos.toByteArray(), "keyframes", null);
	}

	/**
	 * This is kind-of bad, as it creates a new clone of the arrays for
	 * each animation that's being created. However, with the way that jME
	 * currently inflates everything each time you load it, I don't know
	 * how to make it much better without a lot of hacking.
	 */
	public Animation getAnimation(Node target) {
		if (animation.getChildren().size() == 0) {
			recursiveLinkAnimations(target, this);
		}
		return animation;
	}

	final static void recursiveLinkAnimations(Node target, AnimationDataController data) {
		//	Find each child of target that has an animation data controller
		//	link the animations of those nodes. This will deliciously recursively
		//	make sure any other child animations are also linked.
		for (Spatial s : target.getChildren()) {
			if (s instanceof Node) {
				Node n = (Node)s;
				for (Controller c : n.getControllers()) {
					if (c instanceof AnimationDataController) {
						AnimationDataController adc = (AnimationDataController)c;
						Animation a = adc.getAnimation(n);
						data.animation.getChildren().add(a);
						//	only one child animation per node
						break;
					}
				}
			}
		}
	}

	Animation animation;

	Animation makeAnimation() {
		if (keyframes == null || keyframes.size() == 0)
			return new Animation(name, endTime, null, null, null, null);
		float times[] = new float[keyframes.size()];
		Keyframe[] frames = new Keyframe[keyframes.size()];
		int i = 0;
		for (Float f : keyframes.keySet()) {
			times[i] = (float)f;
			frames[i] = keyframes.get(f);
			++i;
		}
		Animation anim = new Animation(name, endTime, times, frames, null, null);
		return anim;
	}

	/**
	 * Find the animation controller on the node, and turn it into an Animation instance,
	 * if available. Else return NULL.
	 * @param node The Node that might have animation.
	 * @return null, or an Animation (with children).
	 */
	public static final Animation findAnimation(Node node) {
		for (Controller c : node.getControllers()) {
			if (c instanceof AnimationDataController) {
				return ((AnimationDataController)c).getAnimation(node);
			}
		}
		return null;
	}
}
